#include <algorithm/analytical_model/io/json/converter/shared/data/topology/geometry/Geometry.h>
#include <algorithm/analytical_model/io/json/transform/TransformData.h>
#include <utils/HasValue.h>

#include <cstdint>

using namespace solar;

#define COMMON_CHECK(input, output)                                                                \
    const auto* value_ptr = (input).if_object();                                                   \
    if (value_ptr == nullptr)                                                                      \
    {                                                                                              \
        return;                                                                                    \
    }                                                                                              \
    auto* new_value_ptr = (output).if_object();                                                    \
    if (new_value_ptr == nullptr)                                                                  \
    {                                                                                              \
        (output).emplace_object();                                                                 \
        new_value_ptr = (output).if_object();                                                      \
    }

#define COMMON_CHECK2(input1, input2, output)                                                      \
    const auto* value_ptr1 = (input1).if_object();                                                 \
    if (value_ptr1 == nullptr)                                                                     \
    {                                                                                              \
        return;                                                                                    \
    }                                                                                              \
    const auto* value_ptr2 = (input2).if_object();                                                 \
    if (value_ptr2 == nullptr)                                                                     \
    {                                                                                              \
        return;                                                                                    \
    }                                                                                              \
    auto* new_value_ptr = (output).if_object();                                                    \
    if (new_value_ptr == nullptr)                                                                  \
    {                                                                                              \
        (output).emplace_object();                                                                 \
        new_value_ptr = (output).if_object();                                                      \
    }

// #define COMMON_CHECK3(input1, input2, input3, output)                                              \
//     const auto* value_ptr1 = (input1).if_object();                                                 \
//     if (value_ptr1 == nullptr)                                                                     \
//     {                                                                                              \
//         return;                                                                                    \
//     }                                                                                              \
//     const auto* value_ptr2 = (input2).if_object();                                                 \
//     if (value_ptr2 == nullptr)                                                                     \
//     {                                                                                              \
//         return;                                                                                    \
//     }                                                                                              \
//     const auto* value_ptr3 = (input3).if_object();                                                 \
//     if (value_ptr3 == nullptr)                                                                     \
//     {                                                                                              \
//         return;                                                                                    \
//     }                                                                                              \
//     auto* new_value_ptr = (output).if_object();                                                    \
//     if (new_value_ptr == nullptr)                                                                  \
//     {                                                                                              \
//         (output).emplace_object();                                                                 \
//         new_value_ptr = (output).if_object();                                                      \
//     }

void solar::io::json::transformScenePara(const io::json::DeCompressTag& /*unused*/,
                                         const boost::json::value& sun_value,
                                         boost::json::value& scene_para_value)
{
    COMMON_CHECK(sun_value, scene_para_value)
    if (const auto* dni_value =
            value_ptr->if_contains("sun_position")->if_object()->if_contains("dni");
        dni_value != nullptr)
    {
        new_value_ptr->insert_or_assign("DNI", dni_value->to_number<double>());
    }
    if (const auto* csr_value =
            value_ptr->if_contains("sun_shape")->if_object()->if_contains("csr");
        csr_value != nullptr)
    {
        new_value_ptr->insert_or_assign("CSR", csr_value->to_number<double>());
    }
}

void solar::io::json::transformCylinder(const io::json::TransformTag& /*unused*/,
                                        const boost::json::value& cylinder_value,
                                        const boost::json::value& position_value,
                                        boost::json::value& cylinder3d_value)
{
    COMMON_CHECK2(cylinder_value, position_value, cylinder3d_value)
    if (const auto* height_value = value_ptr1->if_contains("height"); height_value != nullptr)
    {
        new_value_ptr->insert_or_assign("height", height_value->to_number<double>());
    }
    if (const auto* radius_value = value_ptr1->if_contains("radius"); radius_value != nullptr)
    {
        new_value_ptr->insert_or_assign("radius", radius_value->to_number<double>());
    }
    new_value_ptr->insert_or_assign("center", position_value);
    new_value_ptr->insert_or_assign(
        "type", internal::convertGeometryType(GeometryType::kGeometryCylinder3D));
}

void solar::io::json::transformRectangle(const io::json::TransformTag& /*unused*/,
                                         const boost::json::value& rectangle_value,
                                         const boost::json::value& position_value,
                                         const boost::json::value& normal_value, double thickness,
                                         boost::json::value& rectangle3d_value)
{
    COMMON_CHECK2(rectangle_value, position_value, rectangle3d_value)
    if (const auto* size_value = value_ptr1->if_contains("size"); size_value != nullptr)
    {
        boost::json::value new_size_value = {
            {"x", size_value->at("x").to_number<double>()},
            {"y",                               thickness},
            {"z", size_value->at("y").to_number<double>()}
        };
        new_value_ptr->insert_or_assign("size", new_size_value);
    }
    new_value_ptr->insert_or_assign("center", position_value);
    new_value_ptr->insert_or_assign(
        "type", internal::convertGeometryType(GeometryType::kGeometryRectangle3D));
    const auto* normal_value_ptr = normal_value.if_object();
    if (normal_value_ptr != nullptr)
    {
        new_value_ptr->insert_or_assign("normal", normal_value);
    }
}

void solar::io::json::transformSimulationPara(const io::json::DeCompressTag& /*unused*/,
                                              const boost::json::value& simulation_value,
                                              boost::json::value& simulation_para_value)
{
    COMMON_CHECK(simulation_value, simulation_para_value)
    if (const auto* receiver_pixel_length_value = value_ptr->if_contains("receiver_pixel_length");
        receiver_pixel_length_value != nullptr)
    {
        boost::json::value new_size_value;
        if (receiver_pixel_length_value->is_array())
        {
            const auto& receiver_pixel_length_array = receiver_pixel_length_value->get_array();
            if (receiver_pixel_length_array.size() == 1)
            {
                new_size_value = {
                    {"x", receiver_pixel_length_array[0]},
                    {"y",                     kUndefined}
                };
            }
            if (receiver_pixel_length_array.size() >= 2)
            {
                new_size_value = {
                    {"x", receiver_pixel_length_array[0]},
                    {"y", receiver_pixel_length_array[1]}
                };
            }
        }
        else
        {
            new_size_value = {
                {"x", receiver_pixel_length_value->to_number<double>()},
                {"y",                                       kUndefined}
            };
        }
        new_value_ptr->insert_or_assign("receiver_pixel_size", new_size_value);
    }
    if (const auto* heliostat_pixel_length_value = value_ptr->if_contains("heliostat_pixel_length");
        heliostat_pixel_length_value != nullptr)
    {
        boost::json::value new_size_value;
        if (heliostat_pixel_length_value->is_array())
        {
            const auto& heliostat_pixel_length_array = heliostat_pixel_length_value->get_array();
            if (heliostat_pixel_length_array.size() == 1)
            {
                new_size_value = {
                    {"x", heliostat_pixel_length_array[0]},
                    {"y",                      kUndefined}
                };
            }
            if (heliostat_pixel_length_array.size() >= 2)
            {
                new_size_value = {
                    {"x", heliostat_pixel_length_array[0]},
                    {"y", heliostat_pixel_length_array[1]}
                };
            }
        }
        else
        {
            new_size_value = {
                {"x", heliostat_pixel_length_value->to_number<double>()},
                {"y",                                        kUndefined}
            };
        }
        new_value_ptr->insert_or_assign("micro_heliostat_pixel_size", new_size_value);
    }
}

void solar::io::json::transformCommonPara(const io::json::DeCompressTag& /*unused*/,
                                          const boost::json::value& simulation_value,
                                          boost::json::value& common_para_value)
{
    COMMON_CHECK(simulation_value, common_para_value)
    if (const auto* num_gpu_threads_value = value_ptr->if_contains("num_gpu_threads");
        num_gpu_threads_value != nullptr)
    {
        new_value_ptr->insert_or_assign("num_gpu_threads",
                                        num_gpu_threads_value->to_number<std::uint32_t>());
    }
    if (const auto* path_to_model_value = value_ptr->if_contains("path_to_model");
        path_to_model_value != nullptr)
    {
        new_value_ptr->insert_or_assign("path_to_model", path_to_model_value->get_string());
    }
}

void solar::io::json::transformRectangleGrid(const io::json::TransformTag& /*unused*/,
                                             const boost::json::value& grid_value,
                                             boost::json::value& rectangle_grid_value)
{
    COMMON_CHECK(grid_value, rectangle_grid_value)
    if (const auto* size_value = value_ptr->if_contains("size"); size_value != nullptr)
    {
        new_value_ptr->insert_or_assign("size", *size_value);
    }
    if (const auto* pos_value = value_ptr->if_contains("pos"); pos_value != nullptr)
    {
        new_value_ptr->insert_or_assign("position", *pos_value);
    }
    if (const auto* interval_value = value_ptr->if_contains("interval"); interval_value != nullptr)
    {
        new_value_ptr->insert_or_assign("interval", *interval_value);
    }
}

void solar::io::json::transformHeliostat(const io::json::DeCompressTag& /*unused*/,
                                         const boost::json::value& gap_value,
                                         const boost::json::value& subhelio_row_col_value,
                                         double thickness, bool has_brace,
                                         boost::json::value& heliostat_value)
{
    COMMON_CHECK2(gap_value, subhelio_row_col_value, heliostat_value)
    new_value_ptr->insert_or_assign("gap", *value_ptr1);
    boost::json::value new_subhelio_row_col_value = {
        {"x", subhelio_row_col_value.at("col").to_number<std::uint64_t>()},
        {"y", subhelio_row_col_value.at("row").to_number<std::uint64_t>()}
    };
    new_value_ptr->insert_or_assign("subhelio_row_col", new_subhelio_row_col_value);
    new_value_ptr->insert_or_assign("thickness", thickness);
    new_value_ptr->insert_or_assign("has_brace", has_brace);
}

void solar::io::json::transformHeliostatPara(const io::json::DeCompressTag& /*unused*/,
                                             const boost::json::value& material_value,
                                             boost::json::value& heliostat_para_value)
{
    COMMON_CHECK(material_value, heliostat_para_value)
    if (const auto* reflectivity_value = value_ptr->if_contains("reflectivity");
        reflectivity_value != nullptr)
    {
        new_value_ptr->insert_or_assign("reflectivity", reflectivity_value->to_number<double>());
    }
    if (const auto* sigma_tracking_value = value_ptr->if_contains("sigma_tracking");
        sigma_tracking_value != nullptr)
    {
        new_value_ptr->insert_or_assign("sigma_tracking",
                                        sigma_tracking_value->to_number<double>());
    }
    if (const auto* sigma_value = value_ptr->if_contains("sigma"); sigma_value != nullptr)
    {
        boost::json::value sigma_slope_error_value = {
            {"x", sigma_value->to_number<double>()},
            {"y", sigma_value->to_number<double>()}
        };
        new_value_ptr->insert_or_assign("sigma_slope_error", sigma_slope_error_value);
    }
    if (const auto* slope_value = value_ptr->if_contains("slope"); slope_value != nullptr)
    {
        boost::json::value sigma_slope_error_value = {
            {"x", slope_value->to_number<double>()},
            {"y", slope_value->to_number<double>()}
        };
        new_value_ptr->insert_or_assign("sigma_slope_error", sigma_slope_error_value);
    }
    if (const auto* sigma_slope_error_value = value_ptr->if_contains("sigma_slope_error");
        sigma_slope_error_value != nullptr)
    {
        new_value_ptr->insert_or_assign("sigma_slope_error", *sigma_slope_error_value);
    }
    if (const auto* heliostat_glass_para_value = value_ptr->if_contains("heliostat_glass_para");
        heliostat_glass_para_value != nullptr)
    {
        new_value_ptr->insert_or_assign("heliostat_glass_para", *heliostat_glass_para_value);
    }
    else
    {
        boost::json::value new_heliostat_glass_para_value;
        new_heliostat_glass_para_value.emplace_object();
        auto* new_heliostat_glass_para_value_ptr = new_heliostat_glass_para_value.if_object();
        if (const auto* refractivity_value = value_ptr->if_contains("refractivity");
            refractivity_value != nullptr)
        {
            new_heliostat_glass_para_value_ptr->insert_or_assign(
                "refractivity", refractivity_value->to_number<double>());
        }
        if (const auto* glass_refractive_index_value =
                value_ptr->if_contains("glass_refractive_index");
            glass_refractive_index_value != nullptr)
        {
            new_heliostat_glass_para_value_ptr->insert_or_assign(
                "refractivity", glass_refractive_index_value->to_number<double>());
        }
        if (const auto* sigma_glass_up_slope_error_value =
                value_ptr->if_contains("sigma_glass_up_slope_error");
            sigma_glass_up_slope_error_value != nullptr)
        {
            if (const auto* slope_value = value_ptr->if_contains("slope"); slope_value != nullptr)
            {
                boost::json::value sigma_slope_error_value = {
                    {"x", slope_value->to_number<double>()},
                    {"y", slope_value->to_number<double>()}
                };
                new_heliostat_glass_para_value_ptr->insert_or_assign("sigma_glass_up_slope_error", sigma_slope_error_value);
                new_heliostat_glass_para_value_ptr->insert_or_assign("sigma_glass_down_slope_error", sigma_slope_error_value);
            }
            new_heliostat_glass_para_value_ptr->insert_or_assign(
                "sigma_glass_up_slope_error",
                *sigma_glass_up_slope_error_value);
        }
        if (const auto* glass_thickness_value = value_ptr->if_contains("glass_thickness");
            glass_thickness_value != nullptr)
        {
            const auto glass_thickness_value_num = glass_thickness_value->to_number<double>();
            new_heliostat_glass_para_value_ptr->insert_or_assign("glass_thickness",
                                                                 glass_thickness_value_num);
            if (glass_thickness_value_num > 0)
            {
                new_value_ptr->insert_or_assign("type", "kWithGlass");
            }
        }
        new_value_ptr->insert_or_assign("heliostat_glass_para", new_heliostat_glass_para_value);
    }
}
